%include "boot.inc"
section loader vstart=LOADER_BASE_ADDR  ; loader 会被加载到这个位置，所以指定 vstart 让后续的 $/$$ 能正常定位
LOADER_STACK_TOP equ LOADER_BASE_ADDR   ; 栈往下生长，不覆盖下面的 loader 代码
jmp loader_start    ; 进入 loader

; GDT，下面是 GDT 的表项，每个8字节，第一个项不用
GDT_BASE:   dd 0x00000000       ; 第一项不使用，为了预防忘记初始化，CPU见到这个选择子直接报错
            dd 0x00000000       ; dd 表示 defined double-word
CODE_DESC:  dd 0x0000ffff       ; 代码段，此处的低4字节是段界限的0~15位，都取 ffffh；搞4字节是段基址的0~15位
            dd DESC_CODE_HIGH4
DATA_STACK_DESC:    dd 0x0000ffff   ; 数据段、栈段
                    dd DESC_DATA_HIGH4
VIDEO_DESC: dd 0x80000007   ; limit=(0xbffff-0xb8000) / 4k = 0x7, 显存大小，此处0xb8000~0xbffff为显存文本模式对应的内存区域，可查看实模式内存布局来确定；段基址的0~15位为0x8000，对应显存的位置
            dd DESC_VIDEO_HIGH4 ; dpl 为0

GDT_SIZE    equ $-GDT_BASE
GDT_LIMIT   equ GDT_SIZE-1  ; GDT界限，按字节数运算

times 60 dq 0           ; 预留60个描述符的空位，第个dq是8字节
SELECTOR_CODE     equ (1<<3) + TI_GDT + RPL0  ; (CODE_DESC-GDT_BASE) / 8 + TI_GDT + RPL0，即GDT中的下标再加各种属性
SELECTOR_DATA     equ (2<<3) + TI_GDT + RPL0
SELECTOR_VIDEO    equ (3<<3) + TI_GDT + RPL0

; gdt 指针，前2字节为 gdt界限，后4字节为gdt起始地址
gdt_ptr dw GDT_LIMIT    ; 一共6字节48位，对应 lgdt48
        dd GDT_BASE
loadermsg db '2 loader in real.'

; INT 0x10 功能号：0x13 打印字符串
; 输入
; AH 子功能号=13h
; BH 页码
; BL 属性(AL=00h或者01h时有效)
; CX 字符串长度
; (DH,DL) 坐标（行列）
; ES:BP 字符串地址
; AL 显示输出方式
;   0 - 字符串中只含显示字符，显示属性在BL中；显示后，光标位置不变
;   1 - 字符串中只含显示字符，显示属性在BL中；显示后，光标位置改变
;   2 - 字符串中含显示字符和显示属性。显示后，光标位置不变
;   3 - 字符串中含显示字符和显示属性。显示后，光标位置改变
; 输出：void
loader_start:
    mov sp, LOADER_BASE_ADDR
    mov bp, loadermsg   ; ES:BP 为字符串地址
    mov cx, 17          ; 字符串长度
    mov ax, 0x1301      ; AH=13h, AL=01h
    mov bx, 0x001f      ; BH=00h(页号为0), BL=1fh(蓝底粉红字)
    mov dx, 0x1800      ; 坐标行列(18h,00h)
    int 0x10            ; 10h 中断，这个是 bios 提供的，进入保护模式后将不再使用了

    ; ------ 进入保护模式 -----------
    ; 打开 A20
    ; 加载 gdt
    ; cr0（PE）置为1
    ; -------- 打开 A20 ---------
    in al, 0x92
    or al, 0000_0010b
    out 0x92, al

    ; ------- 加载 GDT ------
    lgdt    [gdt_ptr]

    ; -------- 设置 cr0.pe
    mov eax, cr0
    or eax, 1   ; 最低位
    mov cr0, eax

    jmp dword SELECTOR_CODE:p_mode_start    ; 刷新流水线，主要是将原来的段基址换为选择子
                                            ; 另外，当前IP中已存在了下一条需要执行的实模式指令，为16bits模式，不刷新CPU会出错

[bits 32]   ; 后面进入32位指令段
p_mode_start:
    mov ax, SELECTOR_DATA
    mov ds, ax  ; 所有段寄存器都还是16位，里面放选择子
    mov es, ax
    mov ss, ax
    mov esp, LOADER_STACK_TOP
    mov ax, SELECTOR_VIDEO
    mov gs, ax

    mov byte [gs:160], 'P'

    jmp $

